package ws.jonas.common.web;

import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;

import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import ws.jonas.common.config.Config;
import ws.jonas.common.log.Logger;
import ws.jonas.common.model.Product;
import ws.jonas.common.model.ProductQuantity;
import ws.jonas.common.model.Store;

public class WebQuerier {
	private static final Logger log = Logger.getInstance(WebQuerier.class);

	private WebQuerier() {
	}

	public static List<ProductQuantity> query(String prodnrString) throws NumberFormatException {
		int prodnr = Integer.parseInt(prodnrString);
		String url = String.format(Config.get(Config.QUERY_URL_KEY), prodnr);
		log.info("Querying prodnr " + prodnr);
		Connection conn = null;
		try {
			conn = Jsoup.connect(url);
		} catch (IllegalArgumentException e) {
			log.severe("Bad URL: " + e);
			return null;
		}
		try {
			conn.timeout(Integer.parseInt(Config.get(Config.CONNECTION_TIMEOUT_MS_KEY)));
		} catch (NumberFormatException e) {
			log.warning("Unable to parse connection timeout, taking default value");
			conn.timeout(Integer.parseInt(Config.getDefault(Config.CONNECTION_TIMEOUT_MS_KEY)));
		}

		int retries;
		try {
			retries = Integer.parseInt(Config.get(Config.CONNECTION_RETRIES_KEY));
		} catch (NumberFormatException e) {
			log.warning("Failed to parse connection retries, taking default value");
			retries = Integer.parseInt(Config.getDefault(Config.CONNECTION_RETRIES_KEY));
		}

		Document doc = null;
		for (int i = 1; i <= retries; ++i) {
			try {
				doc = conn.get();
				break;
			} catch (IOException e) {
				log.info("Try #" + i + " failed: " + url.toString());
			}
		}

		if (doc == null) {
			log.warning("Failed to retrieve: " + url.toString());
			return null;
		}

		//Get name
		Elements elements = doc.select("div.beverageContent div.beverageProperties h1");
		String name = elements.text();

		Map<Product, Map<Store, Integer>> quantitiesByStoreByProduct = new LinkedHashMap<Product, Map<Store, Integer>>();

		try {
			//Get volumes (aka products)
			elements = doc.select("div.beverageListInfo table.beverageListTable td.attributename, div.beverageListInfo table.beverageListTable td.attributevalue");
			for (Iterator<Element> iterator = elements.iterator(); iterator.hasNext(); ) {
				Element element = iterator.next();
				if (element.text().equals("Förpackning")) {
					String volume = iterator.next().text().trim();
					quantitiesByStoreByProduct.put(new Product(prodnr, name, volume), new LinkedHashMap<Store, Integer>());
				}
			}

			//Get county, city, address, volume(s)
			elements = doc.select("div#byMap fieldset.store legend span, div#byMap fieldset.store strong, div#byMap fieldset.store span.storeLocation, div#byMap fieldset.store td.amount");

			for (ListIterator<Element> iterator = elements.listIterator(); iterator.hasNext(); ) {
				String county = iterator.next().text().trim();
				while (iterator.hasNext()) {
					Element element = iterator.next();
					if (element.nodeName().equals("span") && !element.className().equals("storeLocation")) {
						//New county found, rewind and break
						iterator.previous();
						break;
					}
					String city = element.text();
					String address = iterator.next().text();
					Store store = new Store(address, city, county);

					for (Map<Store, Integer> quantitiesByStore : quantitiesByStoreByProduct.values()) {
						int quantity;
						String text = iterator.next().text().trim();
						try {
							quantity = Integer.parseInt(text.substring(0, text.indexOf(" ")));
						} catch (NumberFormatException e) {
							log.warning("Unable to parse stock quantity: " + text + ". Skipping");
							log.warning(e);
							continue;
						}
						quantitiesByStore.put(store, quantity);
					}
				}
			}
		} catch (NoSuchElementException e) {
			log.warning("Web page structured unexpectedly.");
			log.warning(e);
			return null;
		}

		List<ProductQuantity> productQuantities = new LinkedList<ProductQuantity>();
		for (Map.Entry<Product, Map<Store, Integer>> entry : quantitiesByStoreByProduct.entrySet()) {
			productQuantities.add(new ProductQuantity(entry.getKey(), entry.getValue()));
		}

		if (quantitiesByStoreByProduct.isEmpty()) {
			log.warning("No information found for prodnr " + prodnr);
		}

		log.info("Query for prodnr " + prodnr + " done");
		return productQuantities;
	}
}
